package bili_live

import (
	"encoding/json"
	"fmt"
	"io"
	"iptv_server/v3/v2_src/utils"
	"net/http"
	"net/http/cookiejar"
	"strconv"
	"time"
)

const (
	api_url string = "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo"
)

var (
	// # 其他 两个 可以 一个是视频, 一个是音频 http_hls_fmp4_avc

	// ##  video: "http_hls_ts_avc"
	// ##  audio: "http_stream_flv_avc"
	// avc hevc
	keys_for_video_url = [...]string{
		"http_hls_fmp4_avc",
		"http_hls_ts_avc",
		"http_stream_flv_avc",
	}
)

func is_video_key(key string) bool {
	for _, v := range keys_for_video_url {
		if v == key {
			return true
		}
	}
	return false
}

type Params struct {
	Code int `json:"code"`
	Data struct {
		Live_status int `json:"live_status"`
		Room_id     int `json:"room_id"`
	}
}

type Result struct {
	Code int `json:"code"`
	Data struct {
		Room_id      int `json:"room_id"`
		Playurl_info struct {
			PlayUrl struct {
				Cid    int `json:"cid"`
				Stream []struct {
					Protocol_name string `json:"protocol_name"`
					Format        []struct {
						Format_name string `json:"format_name"`
						Codec       []struct {
							Codec_name string `json:"codec_name"`
							Current_qn int    `json:"current_qn"`
							Accept_qn  []int  `json:"accept_qn"`
							Base_url   string `json:"base_url"`
							Url_info   []struct {
								Host       string `json:"host"`
								Extra      string `json:"extra"`
								Stream_ttl int    `json:"stream_ttl"`
							} `json:"url_info"`
						} `json:"codec"`
					} `json:"format"`
				} `json:"stream"`
			} `json:"playurl"`
		} `json:"playurl_info"`
	} `json:"data"`
}

func genRequest(realRoomId int) *http.Request {
	req, err := http.NewRequest(http.MethodGet, api_url, nil)
	if err != nil {
		utils.Logger.Debugf("failed: gen request for room id=%d", realRoomId)
		return nil
	}
	req.Header.Add("User-Agent", utils.UA)

	q := req.URL.Query()
	q.Add("room_id", strconv.Itoa(realRoomId))
	q.Add("no_playurl", strconv.Itoa(0))
	q.Add("mask", strconv.Itoa(0))
	// qn=150高清, qn=250超清, qn=400蓝光, qn=10000原画
	q.Add("qn", strconv.Itoa(10000))
	q.Add("platform", "web")
	q.Add("protocol", "0,1") //# http_stream: 0, http_hls: 1
	q.Add("format", "0,1,2") // # flv: 0, ts: 1, fmp4: 2
	q.Add("codec", "0,1")    // # avc: 0, hevc: 1
	req.URL.RawQuery = q.Encode()
	utils.Logger.Debugf("sucess: gen request for room id=%d", realRoomId)
	return req
}

func parseJson(result *Result) (map[string]string, []string) {
	urls := make(map[string]string)
	seq := make([]string, 0, 10)
	streams := result.Data.Playurl_info.PlayUrl.Stream
	for _, stream := range streams {
		protocol_name := stream.Protocol_name
		if len(protocol_name) <= 0 {
			continue
		}
		formats := stream.Format
		for _, format := range formats {
			format_name := format.Format_name
			if len(format_name) <= 0 {
				continue
			}
			codecs := format.Codec
			for _, codec := range codecs {
				//
				codec_name := codec.Codec_name
				base_url := codec.Base_url
				if len(base_url) <= 0 {
					continue
				}
				url_infos := codec.Url_info
				for i, url_info := range url_infos {
					host := url_info.Host
					extra := url_info.Extra
					if len(host) <= 0 || len(extra) <= 0 {
						continue
					}
					key := protocol_name + "_" + format_name + "_" + codec_name
					url := host + base_url + extra

					if is_video_key(key) {
						if utils.IsHttpUrl(url) {
							if i > 0 {
								key = key + "::::" + strconv.Itoa(i)
							}
							urls[key] = url
							seq = append(seq, key)
						}
						utils.Logger.Debugf("bili matched      %s,%s", key, url)
					} else {
						utils.Logger.Debugf("bili not matched  %s,%s", key, url)
					}
				}
			}
		}
	}
	if len(urls) == 0 {
		return nil, nil
	}
	return urls, seq
}

type serverState struct {
	client     *http.Client
	realRoomId int
}

func (state *serverState) getVideoUrls() (map[string]string, []string) {
	req := genRequest(state.realRoomId)

	res, err := state.client.Do(req)
	if err != nil {
		return nil, nil
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		utils.Logger.Debugf("res.StatusCode != http.StatusOK room id=%d", state.realRoomId)
		return nil, nil
	}
	result := Result{
		Code: -1,
	}

	bs, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, nil
	}

	//// TODO Decoder 是否对 不完整的 struct 解析有问题! 待验证
	////     而ReadAll之后 Unmarshal 没为题 ?!

	// decoder := json.NewDecoder(res.Body)
	// if err := decoder.Decode(&result); err != nil {
	// 	return ""
	// }

	if err := json.Unmarshal(bs, &result); err != nil {
		utils.Logger.Debugf("json.Unmarshal(bs, &result); err != nil room id=%d, %v", state.realRoomId, err)
		return nil, nil
	}
	utils.Logger.Debugf("room id=%d, result=%v", state.realRoomId, result)

	if result.Code != 0 {
		utils.Logger.Debugf("result.Code != 0 room id=%d", state.realRoomId)
		return nil, nil
	}
	urls, seq := parseJson(&result)

	return urls, seq
}

func newServerState(roomid int64) (state *serverState) {
	// 先获取直播状态和真实房间号
	url := fmt.Sprintf("https://api.live.bilibili.com/room/v1/Room/room_init?id=%d", roomid)
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil
	}
	req.Header.Add("User-Agent", utils.UA)

	// cookiejar.Jar // client 内共享
	jar, _ := cookiejar.New(nil)
	state = &serverState{
		client: &http.Client{
			Jar:     jar,
			Timeout: time.Second * 10,
		},
	}

	//作用 获取 cookie, 以及房间id
	res, err := state.client.Do(req)
	if err != nil {
		utils.Logger.Debugf("state.client.Do(req) ,err != nil url=%s", url)
		return nil
	}
	defer res.Body.Close()

	bs, err := io.ReadAll(res.Body)
	if err != nil {
		utils.Logger.Debugf("io.ReadAll(res.Body) ,err != nil url=%s", url)
		return nil
	}

	params := Params{Code: -1}
	if err := json.Unmarshal(bs, &params); err != nil {
		utils.Logger.Debugf("json.Unmarshal ,err != nil url=%s", url)
		return nil
	}

	utils.Logger.Debugf("params.Data.Room_id: %v, url=%s", params.Data.Room_id, url)
	// 如何判断成功? Room_id>0
	if params.Data.Room_id == 0 {
		return nil
	}
	state.realRoomId = params.Data.Room_id
	return

	//// 不用复制 cookies了, 直接是client共享
	// cookies := res.Cookies()
	// headers := res.Header.Clone()
}
